package compilers.AST.basics;

import compilers.AST.Node;
import compilers.AST.expression.Expression;
import compilers.AST.expression.Term;
import compilers.AST.types.MyType;
import compilers.AST.types.TypeSpecifier;
import compilers.compiler.PGenerator;
import compilers.exceptions.SemanticalException;
import compilers.symbolTable.SymbolTableArrayElement;
import compilers.symbolTable.SymbolTableElement;
import compilers.symbolTable.SymbolTableScope;

public class ArrayTerm extends Term {
	
	private Identifier fIdentifier;
	private Expression fIndex;
	private TypeSpecifier fConvertTo = null;
	
	public ArrayTerm() {
		
	}
	
	public ArrayTerm(int lineNr) {
		super(lineNr);
	}
	
	public void print(int identLevel) {
		println(identLevel, "ArrayTerm");
		fIdentifier.print(identLevel+1);
		fIndex.print(identLevel+1);
	}
	
	public void checkSemantics(SymbolTableScope currentScope) throws SemanticalException {
		fIdentifier.checkSemantics(currentScope);
		fIndex.checkSemantics(currentScope);
		SymbolTableElement element = currentScope.getElement(fIdentifier.getValue(), fNodeId);
		if (!(element instanceof SymbolTableArrayElement)) {
			throw new SemanticalException("'" + fIdentifier.getValue() + "' is not an array." , fLineNr);
		}
		if ((fIndex.getResultType(currentScope) != TypeSpecifier.INTEGER) && (fIndex.getResultType(currentScope) != TypeSpecifier.FLOAT)) {
			throw new SemanticalException("The index of the array element should be of a numerical type.", fLineNr);
		}
	}
	
	public void setIdentifier(Identifier ident) {
		fIdentifier = ident;
	}
	
	public void setIndex(Expression expression) {
		fIndex = expression;
	}
	
	public int getResultType(SymbolTableScope currentScope) {
		if(this.fConvertTo == null) {
			return currentScope.getElement(fIdentifier.getValue(), fNodeId).getType().getType(currentScope);
		} else {
			return this.fConvertTo.getType(currentScope);
		}
	}
	
	public int getOriginalType(SymbolTableScope currentScope) {
		return currentScope.getElement(fIdentifier.getValue(), fNodeId).getType().getType(currentScope);
	}

	public boolean isConstant(SymbolTableScope currentScope) {
		// Array constants cannot be calculated at compile-time
		return false;
	}

	public MyType calculateValue(SymbolTableScope currentScope) {
		// Array constants cannot be calculated at compile-time
		return null;
	}
	
	public void generatePcode(PGenerator pGen, SymbolTableScope currentScope) {
		// Load value of term on stack
		this.loadActualAddress(pGen, currentScope);
		pGen.movs(1);
		
		//Convert if necessary
		if(this.fConvertTo != null) {
			pGen.conv(TypeSpecifier.getPType(this.getOriginalType(currentScope)), this.fConvertTo.getPType(currentScope));
		}
	}
	
	public void loadActualAddress(PGenerator pGen, SymbolTableScope currentScope) {
		// Load relative start address on stack
		SymbolTableArrayElement element = (SymbolTableArrayElement) currentScope.getElement(fIdentifier.getValue(), fNodeId);
		pGen.lod('a', currentScope.getDepth(), element.getMemoryAddress());
		pGen.conv('a', 'i');
		
		// Convert address to actual start address using nesting depths
		pGen.lda(currentScope.getDepth(), 0);
		pGen.conv('a', 'i');
		pGen.add('i');
		
		// Calculate index value and add it to the actual start address
		if (fIndex.isConstant(currentScope)) {
			pGen.load('i', fIndex.calculateValue(currentScope).valueToString());
		} else {
			fIndex.generatePcode(pGen, currentScope);
		}
		pGen.add('i');
		pGen.conv('i', 'a');
	}
	
	public boolean isArray(SymbolTableScope currentScope) {
		return false;
	} 
	
	public String getArrayIdent(SymbolTableScope currentScope) {
		return null;
	}
	
	public void setConvertTo(TypeSpecifier type) {
		this.fConvertTo = type;
	}
	
	public Identifier getIdentifier() {
		return fIdentifier;
	}

}
